package com.gitee.zycra.future.job.service;

import com.gitee.zycra.future.job.common.bo.CommonResult;
import com.gitee.zycra.future.job.common.bo.JobCalculateBO;
import com.gitee.zycra.future.job.common.bo.PageQuery;
import com.gitee.zycra.future.job.common.converter.JobRunningConverter;
import com.gitee.zycra.future.job.common.dto.RunningStatusDTO;
import com.gitee.zycra.future.job.common.enums.JobCycleEnum;
import com.gitee.zycra.future.job.common.enums.ScheduleRatioEnum;
import com.gitee.zycra.future.job.common.model.JobMetaData;
import com.gitee.zycra.future.job.common.model.JobNode;
import com.gitee.zycra.future.job.common.model.JobRunning;
import com.gitee.zycra.future.job.common.model.JobSchedule;
import com.gitee.zycra.future.job.common.model.JobScheduleLog;
import com.gitee.zycra.future.job.common.param.ManualTriggerParam;
import com.gitee.zycra.future.job.common.param.PageQueryRunningParam;
import com.gitee.zycra.future.job.common.util.DateUtil;
import com.gitee.zycra.future.job.common.vo.JobRunningVO;
import com.gitee.zycra.future.job.manager.JobMetaDataManager;
import com.gitee.zycra.future.job.manager.JobNodeManager;
import com.gitee.zycra.future.job.manager.JobRunningManager;
import com.gitee.zycra.future.job.manager.JobScheduleLogManager;
import com.gitee.zycra.future.job.manager.JobScheduleManager;
import com.gitee.zycra.jdbc.model.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @author zycra
 */
@Slf4j
@Service
public class JobRunningService {

    @Autowired
    private JobRunningManager jobRunningManager;

    @Autowired
    private JobScheduleLogManager jobScheduleLogManager;

    @Autowired
    private JobScheduleManager jobScheduleManager;

    @Autowired
    private JobNodeManager jobNodeManager;

    @Autowired
    private JobMetaDataManager jobMetaDataManager;

    @Autowired
    private JobNodeService jobNodeService;

    /**
     * 根据任务调度ID查询
     *
     * @param pageQuery 查询参数
     * @return 查询到的数据
     */
    public PageResult<JobRunningVO> selectJobRunning(PageQuery<PageQueryRunningParam> pageQuery) {
        PageResult<JobRunning> result = jobRunningManager.selectJobRunning(pageQuery);
        List<JobRunning> data = result.getData();
        List<JobRunningVO> voList = new ArrayList<>();
        for (JobRunning jobRunning : data) {
            voList.add(JobRunningVO.buildJobRunningVO(jobRunning));
        }
        return new PageResult<>(result.getPage(), result.getTotal(), voList);
    }

    /**
     * 更新执行进度
     *
     * @param runningList 执行进度表
     */
    public void updateScheduleRatio(List<RunningStatusDTO> runningList) {
        if (CollectionUtils.isEmpty(runningList)) {
            return;
        }
        for (RunningStatusDTO status : runningList) {
            Integer scheduleRatio = status.getScheduleRatio();
            JobRunning jobRunning = new JobRunning().setId(status.getRunningId()).setScheduleRatio(scheduleRatio);
            if (ScheduleRatioEnum.EXECUTE_FAILED.getCode().equals(scheduleRatio)
                    || ScheduleRatioEnum.EXECUTE_SUCCESS.getCode().equals(scheduleRatio)
                    || ScheduleRatioEnum.CANCELED.getCode().equals(scheduleRatio)) {
                jobRunning.setCompleteTime(DateUtil.getNowLocalDateTime());
            }
            jobRunningManager.updateJobRunning(jobRunning);
        }
    }

    /**
     * 转移已完成任务到执行日志
     */
    @Transactional(rollbackFor = Exception.class)
    public void transferToLog() {
        List<JobRunning> runningList = jobRunningManager.selectForTransfer();
        if (CollectionUtils.isEmpty(runningList)) {
            return;
        }
        List<JobScheduleLog> scheduleLogList = new ArrayList<>();
        List<Long> toDeleteList = new ArrayList<>();
        for (JobRunning running : runningList) {
            toDeleteList.add(running.getId());
            scheduleLogList.add(JobRunningConverter.convertToJobScheduleLog(running));
        }
        jobScheduleLogManager.batchInsertJobScheduleLog(scheduleLogList);
        jobRunningManager.deleteRunning(toDeleteList);
    }

    /**
     * 检查超时任务
     */
    public void syncTimeoutStatus() {
        jobRunningManager.checkTimeoutAndUpdate();
    }

    /**
     * 手动触发任务
     *
     * @param manualTriggerParam 调度参数
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<String> manualTrigger(ManualTriggerParam manualTriggerParam) {
        JobSchedule jobSchedule = jobScheduleManager.selectById(manualTriggerParam.getJobScheduleId());
        if (jobSchedule == null) {
            log.warn("未找到执行配置, manualTriggerParam={}", manualTriggerParam);
            return CommonResult.failed("未找到执行配置");
        }
        JobMetaData jobMetaData = jobMetaDataManager.selectById(jobSchedule.getJobMetaDataId());
        if (jobMetaData == null) {
            log.warn("未找到元数据配置, manualTriggerParam={}", manualTriggerParam);
            return CommonResult.failed("未找到元数据配置");
        }
        JobNode jobNode = jobNodeManager.selectById(manualTriggerParam.getNodeId());
        if (jobNode == null) {
            log.warn("未找到节点, manualTriggerParam={}", manualTriggerParam);
            return CommonResult.failed("未找到节点");
        }
        transferToLog();
        JobRunning running = jobRunningManager.selectByJobScheduleId(jobSchedule.getId());
        if (running != null) {
            log.warn("已有运行中的任务, manualTriggerParam={}", manualTriggerParam);
            return CommonResult.failed("已有运行中的任务");
        }
        JobCalculateBO jobCalculateBO = JobCalculateBO.buildJobCalculateBO(jobSchedule, jobMetaData.getJobCycle());
        LocalDateTime normalScheduleTime = jobScheduleManager.calculateScheduleTime(jobCalculateBO);
        JobRunning jobRunning = new JobRunning()
                .setJobName(jobMetaData.getJobName())
                .setScheduleName(jobSchedule.getScheduleName())
                .setJobClassName(jobMetaData.getJobClassName())
                .setJobScheduleId(jobSchedule.getId())
                .setApplicationName(jobMetaData.getApplicationName())
                .setNodeIp(jobNode.getNodeIp())
                .setScheduleParam(manualTriggerParam.getParam())
                .setScheduleTime(DateUtil.getNowLocalDateTime())
                .setNormalScheduleTime(normalScheduleTime)
                .setRetryTimes(0)
                .setScheduleRatio(ScheduleRatioEnum.TO_TRIGGER.getCode())
                .setTimeoutTime(normalScheduleTime.plusMinutes(JobCycleEnum.getByCode(jobMetaData.getJobCycle()).getMinutes()));
        jobRunningManager.insertJobRunning(jobRunning);
        jobNodeService.notifyNode();
        return CommonResult.success("操作成功", null);
    }

    /**
     * 手动取消任务
     *
     * @param id 执行ID
     * @return 取消结果
     */
    public CommonResult<String> manualCancel(Long id) {
        JobRunning jobRunning = jobRunningManager.selectById(id);
        if (jobRunning == null) {
            return CommonResult.failed("未查到任务信息");
        }
        jobRunningManager.manualCancel(jobRunning);
        jobNodeService.notifyNode();
        return CommonResult.success("取消成功", null);
    }

    /**
     * 分配执行节点
     */
    public void dispatch() {
        LocalDateTime now = DateUtil.getNowLocalDateTime();
        List<JobCalculateBO> calculateBOList = jobScheduleManager.selectJobForCalculate(now, now.plusMinutes(1L));
        for (JobCalculateBO jobCalculateBO : calculateBOList) {
            String excludeIp = jobCalculateBO.getScheduleRatio() == null
                    || !ScheduleRatioEnum.TRIGGER_FAILED.getCode().equals(jobCalculateBO.getScheduleRatio()) ? null : jobCalculateBO.getNodeIp();
            String nodeIp = jobNodeManager.selectToDispatchNode(jobCalculateBO.getApplicationName(), excludeIp);
            if (nodeIp == null) {
                continue;
            }
            JobRunning jobRunning = JobCalculateBO.convertToJobRunning(jobCalculateBO, nodeIp);
            jobRunningManager.insertJobRunning(jobRunning);
        }
    }
}
